/*
 * Copyright (C) 2001-2005 Pleasant nightmare studio
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package org.pleasantnightmare.noraxidium.model;

import org.apache.log4j.Logger;
import org.lwjgl.util.Rectangle;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.pleasantnightmare.noraxidium.SupportContainer;
import org.pleasantnightmare.noraxidium.collision.CollisionBound;
import org.pleasantnightmare.noraxidium.collision.CollisionResult;
import org.pleasantnightmare.noraxidium.collision.RectBound;
import org.pleasantnightmare.noraxidium.math.GameVector2f;
import org.pleasantnightmare.noraxidium.support.DisplaySupport;

import java.util.HashSet;
import java.util.Set;

import static org.lwjgl.util.vector.Vector2f.*;

/**
 * Self-contained piece of non-static game logic. Doesn't have to be rendered, but
 * updates are mandatory. If your game object doesn't require updates, then its probably
 * not a GameObject.
 *
 * @author deus
 * @version 1.0
 * @see Representation Representation
 * @since Jul 8, 2010 11:21:14 AM
 */
public class GameObject implements Updated, Rendered {
  private static final Logger LOGGER = Logger.getLogger(GameObject.class);
  private static final float DEFAULT_SPEED = 1.0F;

  private Set<Controller> controllers;
  private String id;
  private GameVector2f position;
  private boolean visible;
  private SupportContainer supportContainer;
  private int delta;
  private Representation representation;
  private GameVector2f force;
  private float speed;
  private RectBound collisionBound;
  private LevelMap levelMap;

  public GameObject(SupportContainer supportContainer, String id) {
    this(supportContainer, id, new GameVector2f(0.0F, 0.0F));
  }

  public GameObject(SupportContainer supportContainer, String id, GameVector2f position) {
    this(supportContainer, id, position, null);
  }

  public GameObject(SupportContainer supportContainer, String id, GameVector2f position, Representation representation) {
    if (supportContainer == null)
      throw new IllegalArgumentException("supportContainer = null");
    if (id == null)
      throw new IllegalArgumentException("id = null");
    if (position == null)
      throw new IllegalArgumentException("position = null");

    this.supportContainer = supportContainer;
    this.id = id;
    this.position = position;
    this.representation = representation;

    force = new GameVector2f();
    visible = representation != null;
    controllers = new HashSet<Controller>();
    delta = -1;
    speed = DEFAULT_SPEED;
    collisionBound = new RectBound(new Rectangle(), CollisionBound.CollisionType.GAME_OBJECT);
  }

  public LevelMap getLevelMap() {
    return levelMap;
  }

  public void setLevelMap(LevelMap levelMap) {
    this.levelMap = levelMap;
  }

  public CollisionResult detectCollision(CollisionBound collisionBound) {
    return collisionBound.calculateAgainst(this.collisionBound);
  }

  public CollisionBound getCollisionBound() {
    return collisionBound;
  }

  @Override public void update(int delta) {
    this.delta = delta;
    updateControllers();
    boolean moved = updatePosition();
    if (moved) {
      updateCollisionBound();
      resolveSolidCollision();
    }

    force.zero();
  }

  private void resolveSolidCollision() {
    GameVector2f commonExitVector = new GameVector2f();
    commonExitVector.zero();

    for (CollisionBound bound : levelMap.getSolidCollision()) {
      CollisionResult result = detectCollision(bound);
      if (!result.isCollisionDetected())
        continue;
      add(commonExitVector, result.getExitVector(bound), commonExitVector);
    }

    LOGGER.debug("Exit: " + commonExitVector);
//    add(position, commonExitVector, position);
  }

  private void updateCollisionBound() {
    Image image = representation.getImage(delta);
    collisionBound.setWidth(image.getWidth());
    collisionBound.setHeight(image.getHeight());
    collisionBound.setPosition(position);
  }

  private void updateControllers() {
    // TODO This naive implementation will crash with concurrent modification, if we call
    // add/remove during controller update (ie. controller decides to remove itself during update)
    if (controllers != null)
      for (Controller controller : controllers)
        controller.update(delta);
  }

  private boolean updatePosition() {
    // Normalise will crash if we don't do this check, and in the end its cheaper anyways.
    if (force.x == 0.0F && force.y == 0.0F)
      return false;

    force.normalise();
    GameVector2f translator = new GameVector2f(force);
    translator.scale(speed);
    GameVector2f.add(position, translator, position);

    return true;
  }

  @Override public void render(Graphics g) {
    if (representation == null || !isVisible())
      return;

    DisplaySupport displaySupport = supportContainer.getSupport(DisplaySupport.class);
    GameVector2f screenPosition = displaySupport.transformWorldToScreen(getPosition());
    Image image = representation.getImage(delta);
    float sizeOffset = image.getHeight();
    g.drawImage(image, screenPosition.getX(), screenPosition.getY());
  }

  public void addForce(GameVector2f force) {
    GameVector2f.add(this.force, force, this.force);
  }

  public void addController(Controller controller) {
    controllers.add(controller);
    controller.setParent(this);
  }

  public void removeController(Controller controller) {
    controllers.remove(controller);
    controller.setParent(null);
  }

  public void clearControllers() {
    for (Controller controller : controllers)
      controller.setParent(null);
    controllers.clear();
  }

  public String getId() {
    return id;
  }

  public GameVector2f getPosition() {
    return position;
  }

  public void setPosition(GameVector2f position) {
    this.position.set(position);
    updateCollisionBound();
  }

  public boolean isVisible() {
    return visible;
  }

  public void setVisible(boolean visible) {
    if (representation == null)
      throw new IllegalStateException("This game object has no representation, therefore can't be visible.");
    this.visible = visible;
  }

  public Representation getRepresentation() {
    return representation;
  }

  public void setRepresentation(Representation representation) {
    this.representation = representation;
    if (representation == null)
      setVisible(false);
  }

  public float getSpeed() {
    return speed;
  }

  public void setSpeed(float speed) {
    this.speed = speed;
  }
}
